/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <rpc/types.h>

#include "list.h"
#include "common.h"

static int set_time_window(char *, LIST_T *, const char *);

time_t
optarg_2time(char *arg)
{
    if (NULL == arg || 0 == strlen(arg)) {
        log_message(ERR, NOCLASS, "%s: input argument is NULL.", __func__);
        return -1;
    }

    struct tm currentTime, *retTime;
    memset(&currentTime, '\0', sizeof(struct tm));

    time_t now = time(NULL);
    retTime = localtime_r(&now, &currentTime);
    if (NULL == retTime) {
        log_message(ERR, NOCLASS, "%s: localtime_r() failed due to %m.", __func__);
        return -1;
    }

    currentTime.tm_sec = 0;

    char *sp = arg;
    unsigned int numTime = 0;
    unsigned int len = strlen(arg);
    char *strTime = (char *)calloc(len+1, sizeof(char));
    if (NULL == strTime) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed: %m", __func__, len+1);
        return -1;
    }

    char *token;
    while ((token = get_string_token(&sp, "/:", strTime, len+1)) != NULL) {
        len = strlen(sp);
        numTime ++;
    }

    if (2 > numTime) {
        log_message(ERR, NOCLASS, "%s: time <%s> has less : found, must specify at least hh:mm.",
                    __func__, arg);
        FREEUP(strTime);
        return -1;
    }

    if (5 < numTime) {
        log_message(ERR, NOCLASS, "%s: time <%s> has too many :, specify at most yyyy:mon:day:hh:mm.",
                    __func__, arg);
        FREEUP(strTime);
        return -1;
    }

    int inputTime[5] = {-1,-1,-1,-1,-1};
    sp = arg;
    len = strlen(arg);
    while ((token = get_string_token(&sp, "/:", strTime, len+1)) != NULL) {

        len = strlen(sp);
        if (!isinteger(token)) {
            log_message(ERR, NOCLASS, "%s: specify a non-integer <%s> in time <%s>.",
                        __func__, token, arg);
            FREEUP(strTime);
            return -1;
        }

        numTime --;

        const char *logTimeString="unknown";
        int min=-1, max=INFINIT_INT-1;
        switch (numTime) {
        case 4:
            logTimeString = "year";
            break;
        case 3:
            max = 32;
            logTimeString = "month";
            break;
        case 2:
            min = 0;
            max = 32;
            logTimeString = "day";
            break;
        case 1:
            max = 24;
            logTimeString = "hour";
            break;
        case 0:
            max = 60;
            logTimeString = "minute";
            break;
        }

        inputTime[numTime] = gf_atoi(token, max, min);
        if (INFINIT_INT == inputTime[numTime]) {
            log_message(ERR, NOCLASS, "%s: specified %s must between %d and %d.",
                        __func__, logTimeString, min+1, max-1);
            return -1;
        }
    }
    FREEUP(strTime);

    currentTime.tm_min = inputTime[0];
    currentTime.tm_hour = inputTime[1];
    if (0 < inputTime[2]) {
        currentTime.tm_mday = inputTime[2];
    }
    if (0 < inputTime[3]) {
        currentTime.tm_mon = inputTime[3] - 1;
    }
    if (0 < inputTime[4]) {
        currentTime.tm_year = inputTime[4] - 1900;
    }

    currentTime.tm_isdst = -1;

    return (mktime(&currentTime));
} // end function optarg_2time

bool_t
iswindow_active(struct dayhour *dayhour, time_t now, time_t *closetime, LIST_T *winlist)
{
    if (0 == LIST_NUM_ENTS(winlist)) {
        return TRUE;
    }

    bool_t active=FALSE;

    time_t edgeTime=INFINIT_INT, nextCloseTime;
    time_window_t *window;
    for (window=(time_window_t *)winlist->forw; window!=(time_window_t *)winlist;
         window=(time_window_t *)window->forw) {

        if (0 == window->open_dayhour.day) {

            if (dayhour->hour >= window->open_dayhour.hour
                && dayhour->hour < window->close_dayhour.hour) {

                active = TRUE;

                nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            } else {

                nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            }

            continue;
        } else if (dayhour->day == window->open_dayhour.day) {

            if (dayhour->hour >= window->open_dayhour.hour) {

                if (dayhour->day == window->close_dayhour.day) {
                    if (dayhour->hour < window->close_dayhour.hour) {
                        active = TRUE;

                        nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    } else {

                        nextCloseTime = now - (window->open_dayhour.hour - dayhour->hour) * 3600 + 3600*24*7;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    }
                } else {
                    active = TRUE;

                    if (dayhour->day < window->close_dayhour.day) {
                        nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600
                                        + (window->close_dayhour.day - dayhour->day)*3600*24;
                    } else {
                        nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600
                                        + (window->close_dayhour.day - dayhour->day + 7)*3600*24;
                    }

                    if (edgeTime > nextCloseTime) {
                        edgeTime = nextCloseTime;
                    }
                }
            } else {

                nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            }

            continue;
        } else if (dayhour->day < window->open_dayhour.day) {

            if (window->open_dayhour.day <= window->close_dayhour.day) {

                nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                + (window->open_dayhour.day - dayhour->day)*3600*24;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            } else {

                if (dayhour->day > window->close_dayhour.day) {

                    nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                    + (window->open_dayhour.day - dayhour->day)*3600*24;
                    if (edgeTime > nextCloseTime) {
                        edgeTime = nextCloseTime;
                    }
                } else if (dayhour->day < window->close_dayhour.day) {
                    active = TRUE;

                    nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600
                                    + (window->close_dayhour.day - dayhour->day)*3600*24;
                    if (edgeTime > nextCloseTime) {
                        edgeTime = nextCloseTime;
                    }
                } else {
                    if (dayhour->hour >= window->close_dayhour.hour) {

                        nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                        + (window->open_dayhour.day - dayhour->day)*3600*24;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    } else {
                        active = TRUE;

                        nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    }
                }
            }
        } else {
            if (window->open_dayhour.day == window->close_dayhour.day) {

                nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                + (window->open_dayhour.day - dayhour->day + 7)*3600*24;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            } else if (window->open_dayhour.day < window->close_dayhour.day) {

                if (dayhour->day > window->close_dayhour.day) {

                    nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                    + (window->open_dayhour.day - dayhour->day + 7)*3600*24;
                    if (edgeTime > nextCloseTime) {
                        edgeTime = nextCloseTime;
                    }
                } else if (dayhour->day < window->close_dayhour.day) {
                    active = TRUE;

                    nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600
                                    + (window->close_dayhour.day - dayhour->day)*3600*24;
                    if (edgeTime > nextCloseTime) {
                        edgeTime = nextCloseTime;
                    }
                } else {
                    if (dayhour->hour >= window->close_dayhour.hour) {

                        nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                        + (window->open_dayhour.day - dayhour->day)*3600*24;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    } else {
                        active = TRUE;

                        nextCloseTime = now + (window->close_dayhour.hour - dayhour->hour) * 3600;
                        if (edgeTime > nextCloseTime) {
                            edgeTime = nextCloseTime;
                        }
                    }
                }
            } else {
                active = TRUE;

                nextCloseTime = now + (window->open_dayhour.hour - dayhour->hour) * 3600
                                + (window->open_dayhour.day - dayhour->day + 7)*3600*24;
                if (edgeTime > nextCloseTime) {
                    edgeTime = nextCloseTime;
                }
            }
        }
    }

    *closetime = edgeTime;

    return active;
} // end function iswindow_active

char *
parse_time_window(char *window, LIST_T *windowlist, const char *context)
{
    if (NULL == window) {
        log_message(ERR, NOCLASS, "%s: input time window is NULL in %s.",
                    __func__, context);
        return NULL;
    }

    unsigned int len;
    window = strip_spaces(window, FALSE);
    len = strlen(window);
    if (0 >= len) {
        log_message(ERR, NOCLASS, "%s: no valid time window specified in %s.",
                    __func__, context);
        return NULL;
    }

    char *strRetWindow;
    strRetWindow = (char *)calloc(len+1, sizeof(char));
    if (NULL == strRetWindow) {
        log_message(ERR, NOCLASS, "%s: %s(window:%d) failed in %s due to %m.",
                    __func__, "calloc", len+1, context);
        return NULL;
    }

    char *word;
    word = (char *)calloc(len+1, sizeof(char));
    if (NULL == word) {
        log_message(ERR, NOCLASS, "%s: %s(word:%d) failed in %s due to %m.",
                    __func__, "calloc", len+1, context);
        return NULL;
    }

    char *sp = window;
    char *token;
    while ((token = get_string_token(&sp, " ", word, len+1)) != NULL) {

        len = strlen(sp);

        if (set_time_window(token, windowlist, context) < 0) {
            continue;
        }

        if (0 < strlen(strRetWindow)) {
            strcat(strRetWindow, " ");
        }

        strcat(strRetWindow, token);
    }
    FREEUP(word);

    if (0 >= strlen(strRetWindow)) {
        FREEUP(strRetWindow);
        return NULL;
    }

    return (strRetWindow);
} // end function parse_time_window

static int
parse_dayhour(char *window, day_hour_t *dayhour, const char *context)
{
    dayhour->day = 0;
    dayhour->hour = 0.0;

    char *sp = window;
    unsigned int num = 0;
    unsigned int len = strlen(sp);
    char *strDayHour = (char *)calloc(len+1, sizeof(char));
    if (NULL == strDayHour) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed: %m", __func__, len+1);
        return -1;
    }

    char *token;
    while ((token = get_string_token(&sp, ":", strDayHour, len+1)) != NULL) {
        len = strlen(sp);
        num ++;
    }

    if (0 == num || 3 < num) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, no \":\" found or too many \":\" specified.",
                    __func__, token);
        FREEUP(strDayHour);

        return -1;
    }

    sp = window;
    len = strlen(sp);
    token = get_string_token(&sp, ":", strDayHour, len+1);
    if (!isinteger(token) || 0 > atoi(token)) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, must specify an integer.",
                    __func__, token);
        FREEUP(strDayHour);

        return -1;
    }

    switch (num) {
    case 1:
    case 2:
        dayhour->hour = atoi(token);
        if (0.0 > dayhour->hour || 23.0 < dayhour->hour) {

            log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, hour must between 0-23.",
                        __func__, token);
            FREEUP(strDayHour);

            return -1;
        }

        break;
    case 3:
        dayhour->day = atoi(token);
        if (0 == dayhour->day) {
            dayhour->day = 7;
        }

        if (1 > dayhour->day || 7 < dayhour->day) {

            log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, day must between 0-7.",
                        __func__, token);
            FREEUP(strDayHour);

            return -1;
        }

        break;
    }

    if (1 == num) {
        FREEUP(strDayHour);
        return 0;
    }

    len = strlen(sp);
    token = get_string_token(&sp, ":", strDayHour, len+1);
    if (!isinteger(token) || 0 > atoi(token)) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, must specify an integer.",
                    __func__, token);
        FREEUP(strDayHour);

        return -1;
    }

    float min = 0.0;
    switch (num) {
    case 2:
        min = atoi(token);
        if (0 > min || 59 < min) {

            log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, minute must between 0-59.",
                        __func__, token);
            FREEUP(strDayHour);

            return -1;
        }

        dayhour->hour += min/60.0;

        break;
    case 3:
        dayhour->hour = atoi(token);
        if (0.0 > dayhour->hour || 23.0 < dayhour->hour) {

            log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, hour must between 0-23.",
                        __func__, token);
            FREEUP(strDayHour);

            return -1;
        }

        break;
    }

    if (2 == num) {
        FREEUP(strDayHour);
        return 0;
    }

    len = strlen(sp);
    token = get_string_token(&sp, ":", strDayHour, len+1);
    if (!isinteger(token) || 0 > atoi(token)) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, must specify an integer.",
                    __func__, token);
        FREEUP(strDayHour);

        return -1;
    }

    min = atoi(token);
    if (0 > min || 59 < min) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error, minute must between 0-59.",
                    __func__, token);

        FREEUP(strDayHour);

        return -1;
    }

    dayhour->hour += min/60.0;

    FREEUP(strDayHour);

    return 0;
} // end function parse_dayhour

static int
set_time_window(char *window, LIST_T *windowlist, const char *context)
{
    char *sp = window;
    unsigned int num = 0;
    unsigned int len = strlen(sp);
    char *strWindow = (char *)calloc(len+1, sizeof(char));
    if (NULL == strWindow) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed: %m", __func__, len+1);
        return -1;
    }

    char *token;
    while ((token = get_string_token(&sp, "-", strWindow, len+1)) != NULL) {
        len = strlen(sp);
        num ++;
    }

    if (2 != num) {
        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error in %s, must specify one -.",
                    __func__, token, context);
        FREEUP(strWindow);
        return -1;
    }

    day_hour_t openDayHour;
    sp = window;
    len = strlen(sp);
    token = get_string_token(&sp, "-", strWindow, len+1);
    if (parse_dayhour(token, &openDayHour, context) < 0) {
        return -1;
    }

    day_hour_t closeDayHour;
    len = strlen(sp);
    token = get_string_token(&sp, "-", strWindow, len+1);
    if (parse_dayhour(token, &closeDayHour, context) < 0) {
        FREEUP(strWindow);
        return -1;
    }

    FREEUP(strWindow);

    if ((0 == openDayHour.day && 0 != closeDayHour.day)
        || (0 != openDayHour.day && 0 == closeDayHour.day)) {

        log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error in %s, open day and close day are not match.",
                    __func__, window, context);

        return -1;
    }

    if (0 != openDayHour.day && openDayHour.day == closeDayHour.day) {

        if (openDayHour.hour > closeDayHour.hour) {
            log_message(ERR, NOCLASS, "%s: time window <%s> has syntax error in %s, open hour <%d> is after close hour <%d> at the same day.",
                        __func__, token, context, openDayHour.hour, closeDayHour.hour);
            return -1;
        }
    }

    if (NULL == windowlist) {
        return 0;
    }

    time_window_t *timeWindow;
    timeWindow = (time_window_t *)calloc(1, sizeof(struct timeWindow));
    if (NULL == timeWindow) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed: %m", __func__, sizeof(struct timeWindow));
        return -1;
    }

    memcpy(&timeWindow->open_dayhour, &openDayHour, sizeof(struct dayhour));
    memcpy(&timeWindow->close_dayhour, &closeDayHour, sizeof(struct dayhour));
    gf_list_insert(windowlist, windowlist->forw, (LIST_T *)timeWindow);

    return 0;
} // end function set_time_window

void
set_dayhour_4time(struct dayhour *dayhour, time_t nowtime)
{
    char strTimeBuf[TIMESTRINGLEN];

    ctime_2string(CTIME_FORMAT_a_b_d_T, &nowtime, strTimeBuf);
    strTimeBuf[3] = '\0';

    if (strcmp(strTimeBuf, "Sun") == 0) {
        dayhour->day = 7;
    } else if (strcmp(strTimeBuf, "Mon") == 0) {
        dayhour->day = 1;
    } else if (strcmp(strTimeBuf, "Tue") == 0) {
        dayhour->day = 2;
    } else if (strcmp(strTimeBuf, "Wed") == 0) {
        dayhour->day = 3;
    } else if (strcmp(strTimeBuf, "Thu") == 0) {
        dayhour->day = 4;
    } else if (strcmp(strTimeBuf, "Fri") == 0) {
        dayhour->day = 5;
    } else if (strcmp(strTimeBuf, "Sat") == 0) {
        dayhour->day = 6;
    }

    char *strTime = strTimeBuf;
    strTime += 11;

    *(strTime+2) = '\0';
    dayhour->hour = atof(strTime);

    strTime += 3;
    *(strTime+2) = '\0';

    dayhour->hour += atof(strTime) /60.0;

    return;
}
